package domain

import (
	"time"

	"golang.org/x/text/language"
)

type User struct {
	InstanceID     string    `json:"instanceId,omitempty" db:"instance_id"`
	OrganizationID string    `json:"organizationId,omitempty" db:"organization_id"`
	ID             string    `json:"id,omitempty" db:"id"`
	Username       string    `json:"username,omitempty" db:"username"`
	State          UserState `json:"state,omitempty" db:"state"`
	CreatedAt      time.Time `json:"createdAt,omitzero" db:"created_at"`
	UpdatedAt      time.Time `json:"updatedAt,omitzero" db:"updated_at"`

	Machine  *MachineUser    `json:"machine,omitempty" db:"machine"`
	Human    *HumanUser      `json:"human,omitempty" db:"human"`
	Metadata []*UserMetadata `json:"metadata,omitempty" db:"metadata"`
}

type MachineUser struct {
	Name            string                  `json:"name,omitempty" db:"name"`
	Description     string                  `json:"description,omitempty" db:"description"`
	Secret          string                  `json:"secret,omitempty" db:"secret"`
	AccessTokenType PersonalAccessTokenType `json:"access_token_type,omitempty" db:"access_token_type"`

	PATs []*PersonalAccessToken `json:"pats,omitempty" db:"pats"`
	Keys []*MachineKey          `json:"keys,omitempty" db:"keys"`
}

type HumanUser struct {
	FirstName         string       `json:"firstName,omitempty" db:"first_name"`
	LastName          string       `json:"lastName,omitempty" db:"last_name"`
	Nickname          string       `json:"nickname,omitempty" db:"nickname"`
	DisplayName       string       `json:"displayName,omitempty" db:"display_name"`
	PreferredLanguage language.Tag `json:"preferredLanguage,omitzero" db:"preferred_language"`
	Gender            HumanGender  `json:"gender,omitempty" db:"gender"`
	AvatarKey         string       `json:"avatarKey,omitempty" db:"avatar_key"`

	MultifactorInitializationSkippedAt time.Time `json:"multifactorInitializationSkippedAt,omitzero" db:"multifactor_initialization_skipped_at"`

	Email    HumanEmail    `json:"email,omitzero" db:"email"`
	Phone    *HumanPhone   `json:"phone,omitempty" db:"phone"`
	Passkeys []*Passkey    `json:"passkeys,omitempty" db:"passkeys"`
	Password HumanPassword `json:"password,omitzero" db:"password"`
	TOTP     HumanTOTP     `json:"totp,omitzero" db:"totp"`

	IdentityProviderLinks []*IdentityProviderLink `json:"identityProviderLinks,omitempty" db:"identity_provider_links"`

	// Verifications are used if the Login does something on behalf of the user that requires verification
	// e.g. starting passkey registration
	Verifications []*Verification `db:"verifications" json:"verifications,omitempty"`
}

type UserMetadata struct {
	Metadata
	UserID string `json:"userId,omitempty" db:"user_id"`
}

//go:generate enumer -type UserState -transform lower -trimprefix UserState -sql

type UserState uint8

const (
	UserStateUnspecified UserState = iota
	UserStateActive
	UserStateInactive
	UserStateLocked
	UserStateInitial
)

//go:generate enumer -type HumanGender -transform lower -trimprefix HumanGender

type HumanGender uint8

const (
	HumanGenderUnspecified HumanGender = iota
	HumanGenderFemale
	HumanGenderMale
	HumanGenderDiverse
)

type HumanPassword struct {
	// Password is the hashed password
	Password string `json:"-" db:"password"`
	// IsChangeRequired indicates if the user must change their password
	IsChangeRequired bool `json:"isChangeRequired,omitempty" db:"is_change_required"`
	// VerifiedAt is the time when the current password was verified
	VerifiedAt time.Time `json:"verifiedAt,omitzero" db:"verified_at"`
	// Unverified is the verification data for setting a new password
	// If nil, no password change is in progress
	Unverified *Verification `json:"-"`
	// FailedAttempts is the number of consecutive failed password attempts
	// It is reset to 0 on successful verification
	FailedAttempts uint8 `json:"failedAttempts,omitempty" db:"failed_attempts"`
}

type HumanEmail struct {
	Address    string    `json:"address" db:"email"`
	VerifiedAt time.Time `json:"verifiedAt,omitzero" db:"verified_at"`
	OTP        OTP       `json:"-"`
	// Unverified is the verification data for setting a new email
	// If nil, no email change is in progress
	Unverified *Verification `json:"-"`
}

type HumanPhone struct {
	Number     string    `json:"number" db:"phone"`
	VerifiedAt time.Time `json:"verifiedAt,omitzero" db:"verified_at"`
	OTP        OTP       `json:"-"`
	// Unverified is the verification data for setting a new phone number
	// If nil, no phone change is in progress
	Unverified *Verification `json:"-"`
}

type HumanTOTP struct {
	VerifiedAt time.Time `json:"verifiedAt,omitzero" db:"verified_at"`

	LastSuccessfullyCheckedAt time.Time `json:"lastSuccessfullyCheckedAt,omitzero" db:"last_successfully_checked_at"`
	// Check is the verified secret
	Check *Check `json:"-"`
	// Unverified is the unverified secret
	Unverified *Verification `json:"-"`
}

type OTP struct {
	EnabledAt                 time.Time `json:"enabledAt,omitzero" db:"enabled_at"`
	LastSuccessfullyCheckedAt time.Time `json:"lastSuccessfullyCheckedAt,omitzero" db:"last_successfully_checked_at"`
	// Check is the currently active OTP check
	// If nil, no OTP check is active
	Check *Check `json:"-"`
}

type PersonalAccessToken struct {
	ID        string                  `json:"id" db:"id"`
	CreatedAt time.Time               `json:"createdAt,omitzero" db:"created_at"`
	ExpiresAt time.Time               `json:"expiresAt,omitzero" db:"expires_at"`
	Type      PersonalAccessTokenType `json:"type" db:"type"`
	PublicKey string                  `json:"-" db:"public_key"`
	Scopes    []string                `json:"scopes" db:"scopes"`
}

//go:generate enumer -type PersonalAccessTokenType -transform lower -trimprefix PersonalAccessTokenType

type PersonalAccessTokenType uint8

const (
	PersonalAccessTokenTypeUnspecified PersonalAccessTokenType = iota
	PersonalAccessTokenTypeBearer
	PersonalAccessTokenTypeJWT
)

type MachineKey struct {
	ID        string         `json:"id" db:"id"`
	PublicKey []byte         `json:"-" db:"public_key"`
	CreatedAt time.Time      `json:"createdAt,omitzero" db:"created_at"`
	ExpiresAt time.Time      `json:"expiresAt,omitzero" db:"expires_at"`
	Type      MachineKeyType `json:"type" db:"type"`
}

//go:generate enumer -type MachineKeyType -transform lower -trimprefix MachineKeyType

type MachineKeyType uint8

const (
	MachineKeyTypeUnspecified MachineKeyType = iota
	MachineKeyTypeJSON
	MachineKeyTypeNone
)

type Passkey struct {
	ID                           string      `json:"id" db:"id"`
	KeyID                        []byte      `json:"-" db:"key_id"`
	Name                         string      `json:"name" db:"name"`
	SignCount                    uint32      `json:"-" db:"sign_count"`
	PublicKey                    []byte      `json:"-" db:"public_key"`
	AttestationType              string      `json:"attestationType" db:"attestation_type"`
	AuthenticatorAttestationGUID []byte      `json:"aaGuid" db:"authenticator_attestation_guid"`
	Type                         PasskeyType `json:"type" db:"type"`

	CreatedAt  time.Time `json:"createdAt,omitzero" db:"created_at"`
	UpdatedAt  time.Time `json:"updatedAt,omitzero" db:"updated_at"`
	VerifiedAt time.Time `json:"verifiedAt,omitzero" db:"verified_at"`

	// Challenge is used during device registration
	Challenge []byte `json:"-" db:"challenge"`
	// RelyingPartyID is used during device registration
	RelyingPartyID string `json:"-" db:"relying_party_id"`

	// Initialization is used during user registration
	Initialization *Verification `json:"-" db:"initialization"`
}

//go:generate enumer -type PasskeyType -transform lower -trimprefix PasskeyType

type PasskeyType uint8

const (
	PasskeyTypeUnspecified PasskeyType = iota
	PasskeyTypePasswordless
	PasskeyTypeU2F
)

type IdentityProviderLink struct {
	ProviderID       string `json:"providerId" db:"provider_id"`
	ProvidedUserID   string `json:"providedUserId" db:"provided_user_id"`
	ProvidedUsername string `json:"providedUsername" db:"provided_username"`

	CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
	UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
}

//go:generate enumer -type UserType -transform lower -trimprefix UserType

type UserType uint8

const (
	UserTypeUnspecified UserType = iota
	UserTypeHuman
	UserTypeMachine
)
